package org.reborn.engine.render.texture

import org.reborn.utils.niobuffer.BufferUtil
import org.reborn.utils.FileUtil

import java.nio.{Buffer,ByteBuffer,CharBuffer,ShortBuffer,IntBuffer,FloatBuffer,LongBuffer,DoubleBuffer}

import javax.media.opengl.{GL,GL2,GL2GL3,GL2ES2}
import javax.imageio.ImageIO
import java.awt.image.{BufferedImage,Raster}
import java.awt.Image

import TextureType._

trait TextureManager3DSupport {
  self:CachingTextureManager =>
	
  private val TEST_TEX_3D = "Test Texture 3D";
  
  private implicit def defaultTexture3D(gl:GL):Texture3D = TestTexture3D(gl)
  
  /**
   * Create a Texture3D texture, using the default texture metadata.
   * @param gl the GL object so that JOGL operations can be performed
   * @param textureData an array of java.nio.Buffer objects that contain the image data.
   * @param width width of the texture data in pixels
   * @param height height of the texture data in pixels
   * @param depth depth of the texture data in pixels
   * @param glFormat the format of the texture data (GL_RGB, GL_RGBA, GL_LUMINENCE, etc...)
   * @param glType the type of data in the textureData buffer (GL_UNSIGNED_BYTE, GL_INT, GL_FLOAT, etc...)
   * This type must correspond to the type of Buffer implementation used by textureData
   * @return the Texture3D object
   */
  def Texture3D(gl:GL,textureData:Seq[Buffer],width:Int,height:Int,depth:Int,glFormat:Int,glType:Int):Texture3D = {
	val metadata = default3DMetadata
    metadata.width = width
    metadata.height = height
    metadata.depth = depth
    
    return Texture3D(gl,textureData,metadata,glFormat,glType)
  }
  
  /**
   * Create a Texture3D texture. 
   * @param gl the GL object so that JOGL operations can be performed
   * @param textureData an array of java.nio.Buffer objects that contain the image data.
   * @param textureMetadata the metadata of the texture (width, height, depth, internal format, etc...)
   * @param glFormat the format of the texture data (GL_RGB, GL_RGBA, GL_LUMINENCE, etc...)
   * @param glType the type of data in the textureData buffer (GL_UNSIGNED_BYTE, GL_INT, GL_FLOAT, etc...)
   * This type must correspond to the type of Buffer implementation used by textureData
   * @return the Texture3D object
   */
  def Texture3D(gl:GL,textureData:Seq[Buffer],textureMetadata:Texture3DMetadata,glFormat:Int,glType:Int):Texture3D = {
	val textureKey = (getNewBufferFileName,Tex3D)
    val tex:Texture3D = addTextureToMap(gl,textureKey){()=>
      Some(createTexture3DFromBuffer(gl,textureData,textureMetadata,glFormat,glType))
    }
    
    tex
  }
  
  /**
   * Creates a test texture 3D object. This texture is also returned if 
   * there is an error loading a 3D texture from a file
   * @param gl the GL object so that JOGL operations can be performed
   */
  def TestTexture3D(gl:GL):Texture3D = {
	val textureKey = (TEST_TEX_3D,Tex3D)
	
	val tex:Texture3D = addTextureToMap(gl,textureKey){()=>
	  val metadata = new Texture3DMetadata
	  metadata.width=2
	  metadata.height=2
	  metadata.depth=2
	  metadata.magFilter = TextureFilter.Nearest
	  metadata.minFilter = TextureFilter.Nearest
	  metadata.glInternalFormat = GL.GL_RGBA 
	
	  val image = testTextureBuffer
		
	  Some(createTexture3DFromBuffer(gl,Array(image),metadata,GL.GL_RGBA,GL.GL_UNSIGNED_BYTE))
	}
	tex
  }
  
  /**
   * Load an image and create a Texture3D object
   * @param gl the GL object so that JOGL operations can be performed
   * @param fileNames The names of the image files to load as the 3D texture data
   * @param textureMetadata the metadata of the texture (internal format, etc...). The 
   * width and height attributes of the metadata will be overwritten by the actual width
   * and height of the image file.
   * @return the TextureCubeMap object
   */
  def Texture3D(gl:GL,fileNames:Seq[String],textureMetadata:Texture3DMetadata):Texture3D = {
	val textureKey = (fileNames(0),TexCubeMap)
	
	val tex:Texture3D = addTextureToMap(gl,textureKey){()=>
	  val files:Seq[Option[(Int,Int,Int,Int,Buffer)]] = fileNames.map{(fileName)=>
	    getImgDataFromFile(fileName)
	  }
	  
	  if(files.forall(_.isDefined)){
	 	val (width,height,glFormat,glType,buffer) = files(0).get
	 	
	 	val buffer3D = BufferUtil.createDirectByteBuffer(width * height * files.size * 4)
	 	
	 	for(file <- files){
	 	  val (w,h,glFmt,glT,imgBuff) = file.get
	 	  imgBuff.rewind
	 	  buffer3D.put(imgBuff.asInstanceOf[ByteBuffer])
	 	}
	 	
	 	textureMetadata.width = width
	    textureMetadata.height = height
	    textureMetadata.depth = files.size
	    textureMetadata.glInternalFormat = glFormat
	    Some(createTexture3DFromBuffer(gl,Array(buffer3D),textureMetadata,glFormat,glType))
	  }
	  else{
	    System.err.println("Could not find one of the specified 3D Images, " +
	    		"or the images were not in a supported format: "+fileNames(0))
	    None
	  }
	}
	
	tex
  }
  
  /**
   * Load an image and create a TextureCubeMap object using the default metadata
   * @param gl the GL object so that JOGL operations can be performed
   * @param fileNames The names of the image files to load. Must be in the 
   * following order: Negative X, Positive X, Negative Y, Positive Y, Negative Z, Positive Z
   * @return the TextureCubeMap object
   */
  def Texture3D(gl:GL,fileNames:Seq[String]):Texture3D = {
	val metadata = default3DMetadata
	Texture3D(gl,fileNames,metadata)
  }
  
  override protected def testTextureBuffer:Buffer = {
	val image = BufferUtil.createDirectByteBuffer(8 * 4)
	for(i <- 0 until image.capacity){
	  image.put(0x80.asInstanceOf[Byte])
	}
	image.put(0 + 0, 0xFF.asInstanceOf[Byte])
	image.put(4 + 1, 0xFF.asInstanceOf[Byte])
	image.put(8 + 2, 0xFF.asInstanceOf[Byte])
	
	image.put(16 + 0, 0xFF.asInstanceOf[Byte])
	image.put(16 + 1, 0xFF.asInstanceOf[Byte])
	image.put(20 + 1, 0xFF.asInstanceOf[Byte])
	image.put(20 + 2, 0xFF.asInstanceOf[Byte])
	image.put(24 + 2, 0xFF.asInstanceOf[Byte])
	image.put(24 + 0, 0xFF.asInstanceOf[Byte])
	
	image.put(28 + 0, 0xFF.asInstanceOf[Byte])
	image.put(28 + 1, 0xFF.asInstanceOf[Byte])
	image.put(28 + 2, 0xFF.asInstanceOf[Byte])
	
	image.rewind
  }
  
  /**
   * Creates a Texture3DMetadata object with the current TextureManager 
   * metadata values. This metadata object will also be used for any
   * texture creation operation where metadata is not explicitly passed
   * as a parameter.
   */
  def default3DMetadata:Texture3DMetadata = {
	val metadata = new Texture3DMetadata
	updateCommonMetadata(metadata)
	return metadata
  }
  
  protected def createTexture3DFromBuffer(gl:GL,textureData:Seq[Buffer],textureMetadata:Texture3DMetadata,glFormat:Int,glType:Int):Texture3D = {
    val text = new Texture3D(textureMetadata)
    text.createTexture(gl,textureData,glFormat,glType)
    return text
  }
}